<?php
/*
# $Id: Model.php 10 2006-11-18 12:34:32Z James $
*/

/*
# @class Model
*/
class Model extends Object {

	/*
	# @property array $dbData
	# Holds the value of each field in this Model.
	# If any of the fields point to another Model, then an instance of that Model type may be stored in that field within this array.
	#
	# Any changes to the fields in this array may need to be reflected in the Models stored in $this->foreignModels (see below).
	# This is handled automatically within the __call() method.
	*/
	protected $dbData = array();

	/*
	# @property bool $hasChanged
	# Set to TRUE if any data elements are altered.
	*/
	private $hasChanged = FALSE;

	/*
	# @property array $hasLoadedRelatedModels
	# Whenever a call to $this->loadRelatedModels() is executed, a flag is set in this array to
	# indicate that data has already been loaded form persistent storage and should not be loaded again.
	# This array is indexed by Model names.
	# The flag is only set if ALL records have been loaded form the database - ie. the data source is exhausted.
	*/
	private $hasLoadedRelatedModels = array();

	/*
	# @property bool $isInDatabase
	# Flag to indicate if this Model record already exists in the database
	*/
	private $isInDatabase = FALSE;

	/*
	# @property string $modelName
	# Stores the Model's name (ie. everything before the class name's "Model" suffix).
	*/
	public $modelName = '';

	/*
	# @property array $relatedModels
	# Holds any Models that have a relationship to $this Model.
	# The indicies of this array correspond to the ModelRelation class constants.
	# For Models loaded from the database, they are added to these lists in ascending order
	# of their ID - as far as possible. This is meant to aid searching through larger lists.
	*/
	private $relatedModels = array(
		ModelRelation::NONE=>array(),
		ModelRelation::ONE_TO_ONE=>array(),
		ModelRelation::ONE_TO_MANY=>array(),	// Each element is an 2d-array of Models. 1st index is foreign model name. 2nd index is the relation reference.
		ModelRelation::MANY_TO_ONE=>array(),	// Indicies are foreign-key field names, elements are a Model.
		ModelRelation::MANY_TO_MANY=>array()
	);

	private $hasCompositePrimaryKey = FALSE;

	/*
	# -------------------- OVERLOADABLE PROPERTIES --------------------
	# ALL THE PROPERTIES BELOW CAN BE DECLARED IN THE INHERITED CLASSES,
	# OVERWRITING THESE DEFAULT VALUES.
	# -----------------------------------------------------------------
	*/

	/*
	# @property string $dbConnectionName
	# Name of the database connection used by this Model.
	*/
	protected $dbConnectionName = 'default';

	/*
	# @property string $dbTableName
	# The name of the database table that is used to store records of this Model type.
	# If not specified in the extended class then we will attempt to guess the table name.
	#	eg. ModelName becomes model_name
	*/
	protected $dbTableName = NULL;

	/*
	# @property string $dbTablePrimaryKey
	# The name of the field(s) that make up this table's primary key.
	# If the primary key is made up of several fields, then separate them each
	# with a comma.
	*/
	protected $dbTablePrimaryKey = 'id';

	/* -------------------- METHODS -------------------- */

	/*
	# @method Model __construct( [string $modelName] )
	# $modelName	= Used when explicitly defining the Model name, ie. when a class doesn't exist for a Model
	#
	# Constructor.
	*/
	function __construct($modelName=NULL) {

		parent::__construct();

		// Store the Model's name (either from the given argument or based on the class name)
		$this->modelName = preg_replace("/Model$/", "", $modelName===NULL ? get_class($this) : $modelName);

		// Guess the DB table name if it hasn't been explicitly defined in the extended class' $dbTableName property
		if($this->dbTableName===NULL) {
			$this->dbTableName = Inflector::modelName_dbTableName($this->modelName);
		}

		// Ensure a default is set in the primary-key field(s)
		$this->hasCompositePrimaryKey = strpos($this->dbTablePrimaryKey, ",")!==FALSE;
		if($this->hasCompositePrimaryKey) {
			$keys = explode(",", $this->getPrimaryKey());
			$pk = array();
			foreach($keys as $key) {
				$pk[$key] = NULL;
			}
			$this->setPrimaryKeyValue($pk);
		}
		else {
			// TODO: Really, we need to set the initial primary key value to NULL,
			// but that will probably affect relation functions.
			// Also calls to isset($model->id) will return FALSE (because NULL is considered not set),
			// but $m->id===NULL will work for testing value of field.
			// Auto-inc fields in SQLite need to be NULL to increment properly
			$this->setPrimaryKeyValue(0);
		}
	}

	/*
	# @method Model create( string $modelName )
	# $modelName	= Name of the type of Model you want to create (in Camelized format)
	#
	# This is a Factory method for generating new instances of Model classes.
	*/
	static public function create($modelName=NULL) {

		// Keep track of the classes used to represent Models of type $modelName
		static $classNames = array();

		// Get Model class name
		$modelClassName = isset($classNames[$modelName]) ? $classNames[$modelName] : Inflector::modelName_modelClass($modelName);

		// Create and return a new instance of the required Model class
		try {
			// Create Model instance
			$model = new $modelClassName($modelName);

			// Return instance
			$classNames[$modelName] = $modelClassName;
			return $model;
		}
		catch(BuanException $e) {

			// Log
			SystemLog::add('Cannot find class for "'.$modelName.'" Model. Using "Model" class instead.', SystemLog::INFO);

			// Create Model instance
			$model = new Model($modelName);

			// Return instance
			$classNames[$modelName] = "Model";
			return $model;
		}
	}

	/*
	# @method ModelManager getModelManager()
	#
	# Returns an instance of the ModelManager that is used to manage Model of $this type.
	*/
	function getModelManager() {

		// Return ModelManager class instance that is used for transactions for $this Model type
		return ModelManager::create($this->modelName);
	}

	/*
	# @method bool __isset( string $fieldName )
	# $fieldName	= Field to check
	#
	# Returns TRUE is the given field has been defined in $this->dbData, or FALSE otherwise.
	*/
	public function __isset($fieldName) {

		// Result
		return isset($this->dbData[$fieldName]);
	}

	/*
	# @method void __unset( string $fieldName )
	# $fieldName	= Field to unset
	#
	# Removes the specified entry from $this->dbData.
	*/
	public function __unset($fieldName) {

		// Result
		if(isset($this->dbData[$fieldName])) {
			unset($this->dbData[$fieldName]);
		}
	}

	/*
	# @method mixed __get( string $fieldName )
	# $fieldName	= Field name
	#
	# Returns the current value of the requested field.
	*/
	function __get($fieldName) {

		// If a getter method exists for the field, then call it.
		// The get_class_methods() call is needed for case-sensitive matches.
		$methodName = 'get'.Inflector::modelField_classMethod($fieldName);
		if(method_exists($this, $methodName) && in_array($methodName, get_class_methods($this), TRUE)) {
			return $this->$methodName();
		}

		// If a value for this field doesn't exist then create and return it
		if(!isset($this->dbData[$fieldName])) {
			return $this->dbData[$fieldName] = NULL;
		}

		// Return value
		if(is_array($this->dbData[$fieldName])) {
			$this->dbData[$fieldName] = new ArrayObject($this->dbData[$fieldName], ArrayObject::ARRAY_AS_PROPS);
			return $this->dbData[$fieldName];
		}
		return $this->dbData[$fieldName];
	}

	/*
	# @method bool __set( string $fieldName, mixed $fieldValue )
	# $fieldName	= Field name
	# $fieldValue	= Field value
	#
	# Store the given value in the specified field.
	# This magic method fowards the request onto a matching "set*()" method, if it exists, otherwise
	# it simply stores the field value in $this->dbData.
	*/
	function __set($fieldName, $fieldValue) {

		// Create a static cache to hold all the fields that can simply be
		// directly copied to $this->dbData.
		// This improves performance by about 60%.
		static $simpleField = array();
		if(isset($simpleField[$this->modelName.'|'.$fieldName])) {
			if(!isset($this->dbData[$fieldName]) || $this->dbData[$fieldName]!=$fieldValue) {
				$this->dbData[$fieldName] = $fieldValue;
				$this->hasChanged(TRUE);
			}
			return TRUE;
		}

		// Determine if primary-key field is being set in which case forward the
		// call to $this->setprimaryKeyValue()
		if($this->hasCompositePrimaryKey() && in_array($fieldName, explode(",", $this->getPrimaryKey()))) {
			return $this->setPrimaryKeyValue($fieldName, $fieldValue);
		}
		else if($fieldName===$this->getPrimaryKey()) {
			return $this->setPrimaryKeyValue($fieldValue);
		}

		// Call "set*()" method, if it exists
		// For this to work, $fieldName must be lower_underscored (eg.
		// "some_field") and the corresponding method must be UpperCamelCaps
		// (eg. "setSomeField()")
		$result = TRUE;
		$methodName = 'set'.Inflector::modelField_classMethod($fieldName);
		if(method_exists($this, $methodName) && in_array($methodName, get_class_methods($this), TRUE)) {
			$oldValue = isset($this->dbData[$fieldName]) ? $this->dbData[$fieldName] : NULL;
			$result = $this->$methodName($fieldValue);
			if($this->dbData[$fieldName]!=$oldValue) {
				$this->hasChanged(TRUE);
			}
		}

		// Store value in $this->dbData
		else if(!isset($this->dbData[$fieldName]) || $this->dbData[$fieldName]!=$fieldValue) {

			// Store new value
			$this->dbData[$fieldName] = $fieldValue;
			$this->hasChanged(TRUE);

			// Flag this field as being simple
			$simpleField[$this->modelName.'|'.$fieldName] = TRUE;

			// Result
			$result = TRUE;
		}

		// $fieldName might be a foreign-key, so check if there are any Models
		// in $this->relatedModels[M:1] that are linked via $fieldName.
		// If the foreign-key is set to 0 (zero), then we also need to remove
		// that foreign-key's entry in $this->relatedModels.
		//
		// NOTE: You cannot change the ID of a Model that has been loaded from
		// persistent storage.
		//
		// TODO: What about handling foreign-keys that are NOT integers?
		//
		if(isset($this->relatedModels[ModelRelation::MANY_TO_ONE][$fieldName])) {

			// Unflag this field as being simple
			if(isset($simpleField[$this->modelName.'|'.$fieldName])) {
				unset($simpleField[$this->modelName.'|'.$fieldName]);
			}

			// Handle relations
			$relatedModel = $this->relatedModels[ModelRelation::MANY_TO_ONE][$fieldName];
			$fieldValue = (int)$fieldValue;
			if($fieldValue>0 && $relatedModel->getPrimaryKeyValue()!=$fieldValue) {
				$this->removeRelatedModel($relatedModel);
				$newRelatedModel = Model::create($relatedModel->modelName);
				$newRelatedModel->setPrimaryKeyValue($fieldValue);
				$newRelatedModel->getModelManager()->load($newRelatedModel);
				$this->addRelatedModel($newRelatedModel);
			}
		}

		// Catch-all result
		return $result;
	}

	/*
	# @method mixed __call( string $method, array $args )
	# $method	= Class method name
	# $args		= Method arguments
	#
	# Captures calls to any missing class methods and handles them accordingly.
	# "set*()" and "add*()" methods passed through to "$this->addRelatedModel()".
	# "get*()" methods passed through to "$this->getRelatedModels()".
	# "loadAndGet*()" methods load related models then pass through to "$this->getRelatedModels()".
	# "remove*()" methods passed through to "$this->removeRelatedModel()".
	*/
	function __call($method, $args) {

		// Handle "set*" and "add*" methods
		$methodPrefix = substr($method, 0, 3);
		if($methodPrefix=='set' || $methodPrefix=='add') {

			// If the passed argument is a Model, then pass through to the $this->addRelatedModel()
			if(is_object($args[0]) && is_subclass_of($args[0], 'Model')) {
				return $this->addRelatedModel($args[0]);
			}

			// Log
			SystemLog::add("Class method ".get_class($this)."::".$method."() doesn't exist.", SystemLog::CORE);
			return NULL;
		}

		// Handle "get*" methods
		if($methodPrefix=="get") {

			// Extract Model name from $method (ie. everything after "get"
			$modelName = substr($method, 3);

			// Retrieve all related Models
			$models = $this->getRelatedModels($modelName);

			// Result
			return $models;
		}

		// Handle "loadAndGet*" methods
		if(substr($method, 0, 10)=="loadAndGet") {
			$modelName = substr($method, 10);
			$this->loadRelatedModels($modelName, isset($args[0]) ? $args[0] : NULL, isset($args[1]) ? $args[1] : NULL);
			$models = $this->getRelatedModels($modelName, isset($args[1]) ? $args[1] : NULL);
			return $models;
		}

		// Handle "remove" methods
		if(substr($method, 0, 6)=="remove") {

			// If the passed argument is a Model, then pass through to the $this->removeRelatedModel()
			if(is_object($args[0]) && is_subclass_of($args[0], 'Model')) {
				return $this->removeRelatedModel($args[0]);
			}

			// Log
			SystemLog::add("Class method ".get_class($this)."::".$method."() doesn't exist.", SystemLog::CORE);
			return NULL;
		}
	}

	/*
	# @method void addRelatedModel( Model $model, [string $relationRef, [string $reverseRelationRef, [bool $cancelBubble]]] )
	# $model				= The Model that will become related to $this Model
	# $relationRef			= The specific relationship reference that we'll use when adding $model (only required if several native keys point to same Model type)
	# $reverseRelationRef	= The inverse of $relationRef (REQUIRED only for recursive M:M relationships)
	# $cancelBubble			= Used to prevent recursion (INTERNAL USE ONLY)
	#
	# Store the $model in the appropriate $this->relatedModels array.
	# Thanks to $this->__call(), you can also call more user-friendly methods
	# which actually get passed through to this method.
	# eg.	$A->addBlogEntry($entry) ... passes through to ... $A->addRelatedModel($entry);
	#
	# Duplicate associations are prevented, except in the case of M:M linking
	# Models.
	#
	# There is an important note to make when it comes to adding related Models in a
	# recursive 1:M relationship ... in this method, unless $relationRef is specified then 
	# $model is ALWAYS considered the "M" side of the relationship and $this is the "1" side.
	# ie.	calling "$A->addRelatedModel($B)" will add $B to $A->relatedModels[1:M]
	*/
	public function addRelatedModel($model, $relationRef=ModelRelation::REF_DEFAULT, $reverseRelationRef=NULL, $cancelBubble=FALSE) {

		// Determine the relationship between $this and $model
		$relation = ModelRelation::getRelation($this->modelName, $model->modelName, $relationRef);

		if($reverseRelationRef!==NULL) {
			$reverseRelation = ModelRelation::getRelation($this->modelName, $model->modelName, $reverseRelationRef);
		}

		// If the relationship is M:M, we need to create an instance of the
		// appropriate linking Model and associate $this and $model to that
		// instance.
		if($relation->isManyToMany()) {

			// First, check if we've already got a linking Model instance that contains the two instances, $this and $model
			$linkModels = $this->getRelatedModels($relation->getLinkModel(), $relation->getReference());
			foreach($linkModels as $linkModel) {
				$foreignModel = $linkModel->getRelatedModelsByCardinality(ModelRelation::MANY_TO_ONE);
				$foreignKey = $linkModel->getForeignKey($model, $relation->getReference());
				if(isset($foreignModel[$foreignKey]) && $foreignModel[$foreignKey]===$model) {
					// Already present, don't add anything
					return NULL;
				}
			}

			// Create a new instance of the linking Model and associate it with $this and $model
			$linkModel = Model::create($relation->getLinkModel());
			if($relation->isRecursive()) {

				// If no relation references have been specified, then we first
				// attempt to use the REF_PARENT/REF_CHILD pairing.
				// If these fail then stop.
				if($reverseRelationRef===NULL) {
					$reverseRelationRef = $relationRef===ModelRelation::REF_PARENT ? ModelRelation::REF_CHILD : ($relationRef===ModelRelation::REF_CHILD ? ModelRelation::REF_PARENT : NULL);
					if($reverseRelationRef===NULL) {
						SystemLog::add('When adding a related Model via a M:M relationship, you must specify the reverse relation reference (usually REF_PARENT or REF_CHILD).', SystemLog::WARNING);
						return NULL;
					}
				}
				$this->addRelatedModel($linkModel, $relationRef);
				$linkModel->addRelatedModel($model, $reverseRelationRef);
			}
			else {
				$linkModel->addRelatedModel($this, $relation->getReference());
				$linkModel->addRelatedModel($model, $relation->getReference());
			}
			return NULL;
		}

		// Deal with recurive relationships
		// Here we do various checks to make sure that both the 1:M and M:1
		// entries in $this->relatedModels are populated instead of recursively
		// populating 1:M, 1:M, 1:M, etc.
		if($relation->isRecursive()) {

			// 1:M
			if($relation->isOneToMany()) {
				$foreignModels = $model->getRelatedModelsByCardinality(ModelRelation::ONE_TO_MANY);
				if(isset($foreignModels[$this->modelName][$relation->getReference()]) && in_array($this, $foreignModels[$this->modelName][$relation->getReference()], TRUE)) {
					$relation = $relation->getInverseRelation();
				}
			}

			// M:1
			else if($relation->isManyToOne()) {
				$foreignModels = $model->getRelatedModelsByCardinality(ModelRelation::MANY_TO_ONE);
				$fKey = $this->getForeignKey($model);
				if(isset($foreignModels[$fKey]) && $foreignModels[$fKey]===$this) {
					$relation = $relation->getInverseRelation();
				}
			}
		}

		// M:1
		if($relation->isManyToOne()) {

			// Create/update entry in $this->relatedModels
			$cardinality = $relation->getCardinality();
			$ref = $relation->getReference();
			$foreignKey = $this->getForeignKey($model, $ref);
			if(isset($this->relatedModels[$cardinality][$foreignKey])) {
				if($this->relatedModels[$cardinality][$foreignKey]!==$model) {
					// First, remove $this from the Model to which it's foreign-key is currently pointing
					$this->relatedModels[$cardinality][$foreignKey]->removeRelatedModel($this);
				}
				else {
					// Don't need to do anything as $model is already related to $this
					return NULL;
				}
			}
			$this->relatedModels[$cardinality][$foreignKey] = $model;
			if(!$cancelBubble) {
				$model->addRelatedModel($this, $relationRef, $reverseRelationRef, TRUE);
			}
		}

		// 1:M (and 1:1)
		if($relation->isOneToMany()) {

			// Create an entry for the type of $model in the 1:M relations
			// array, if none exists
			$cardinality = $relation->getCardinality();
			$ref = $relation->getReference();
			if(!isset($this->relatedModels[$cardinality][$model->modelName][$ref])) {
				$this->relatedModels[$cardinality][$model->modelName][$ref] = array();
			}

			// Add $model to the 1:M relations array (if it isn't already)
			$alreadyInArray = FALSE;
			if(!$model->isInDatabase()) {
				$alreadyInArray = in_array($model, $this->relatedModels[$cardinality][$model->modelName][$ref], TRUE);
			}
			else {
				foreach($this->relatedModels[$cardinality][$model->modelName][$ref] as $rModel) {
					if($model->getPrimaryKeyValue()==$rModel->getPrimaryKeyValue()) {
						$alreadyInArray = TRUE;
						break;
					}
				}
				reset($this->relatedModels[$cardinality][$model->modelName][$ref]);
			}
			if(!$alreadyInArray) {

				// If $model already has a defined parent (the 1 in 1:M), and
				// it's not $this, then we first need to disband that
				// relationship
				$foreignKey = $model->getForeignKey($this, $ref);
				$currentParentModel = $model->getRelatedModelsByCardinality(ModelRelation::MANY_TO_ONE);
				if(isset($currentParentModel[$foreignKey]) && $currentParentModel[$foreignKey]!==$this) {
					$currentParentModel[$foreignKey]->removeRelatedModel($model);
				}

				// Set $model's foreign-key and then add it to $this Model's
				// list of related Models
				$model->{$foreignKey} = $this->isInDatabase() ? $this->getPrimaryKeyValue() : -1;
				$this->relatedModels[$cardinality][$model->modelName][$ref][] = $model;

				// If the relationship has a limit (eg. 1:1 (1:M,1), 1:M,6, etc)
				// and it has been exceded, then purge the first Model in the
				// list (ie. the oldest set relation)
				$limit = $relation->getLimit();
				if($limit!==NULL && count($this->relatedModels[$cardinality][$model->modelName][$ref])>$limit) {
					$removedModel = array_shift($this->relatedModels[$cardinality][$model->modelName][$ref]);
					$removedModel->removeRelatedModels($this);
				}

				// Add other side of relationship, if bubbling
				if(!$cancelBubble) {
					$model->addRelatedModel($this, $ref, NULL, TRUE);
				}
			}
		}
	}

	/*
	# @method void setRelatedModel( Model $model )
	# $model	= The Model to be stored
	#
	# This is an alias for "$this->addRelatedModel()" because sometimes using "set" instead of "add"
	# is better suited, liguistically, to the calling context.
	# Other than that, the two methods are identical.
	*/
	public function setRelatedModel($model, $relationRef=ModelRelation::REF_DEFAULT, $reverseRelationRef=NULL) {

		// Result
		return $this->addRelatedModel($model, $relationRef, $reverseRelationRef);
	}

	/*
	# @method array|Model|NULL getRelatedModels( string $modelName, [string $relationRef] )
	# $modelName	= Specific target Model
	# $relationRef	= Relationship reference
	#
	# Returns all related Models of type $modelName and based on the given
	# relationship reference.
	#
	# This method only returns Models that are currently loaded into
	# "$this->relatedModels" and DOES NOT load anything from persistent storage
	# (see "$this->loadRelatedModels()" for that facility).
	#
	# As with "addRelatedModel()", overloading allows you to use user-friendly
	# method names to retreive certain Models:
	# eg. $A->getClient() .. passes through to .. $A->getRelatedModels('Client')
	#
	# For 1:M,1 and M:1 relations, a single Model instance will be returned, or
	# NULL if there is no related Model.
	#
	# For M:M relationships, an array will be returned containing all related
	# Models or empty if none were found.
	#
	# If $relationRef is NOT specified and only a "__default__" reference exists
	# in the relationship then that reference will be returned.
	# Otherwise an array, indexed by reference code, is returned.
	#
	# Recursive 1:M relations:
	# You MUST specify a $relationRef because otherwise the function can't know
	# which side of the relation you want to retrieve Models from (ie 1 or M).
	# If you don't specify $relationRef, then an array indexed with all
	# relation references will be returned.
	*/
	public function getRelatedModels($modelName, $relationRef=NULL) {

		// Get the relationship between $this and $modelName
		$relation = ModelRelation::getRelation($this->modelName, $modelName, $relationRef);

		// Handle multiple references
		$models = array();
		if(is_array($relation)) {
			if(isset($relation[ModelRelation::REF_DEFAULT]) && count($relation)==1) {
				$relation = $relation[ModelRelation::REF_DEFAULT];
			}
			else {
				foreach($relation as $ref=>$rel) {
					$rModels = $this->getRelatedModels($modelName, $ref);
					$models[$ref] = $rModels;
				}
				return $models;
			}
		}
		$relationRef = $relation->getReference();

		// Retrieve all related Models
		switch($relation->getCardinality()) {

			// No relationship
			case ModelRelation::NONE:
				break;

			// 1:1
			case ModelRelation::ONE_TO_ONE:
				break;

			// 1:M
			case ModelRelation::ONE_TO_MANY:
				if(isset($this->relatedModels[ModelRelation::ONE_TO_MANY][$modelName][$relationRef])) {
					$relatedModels = $this->relatedModels[ModelRelation::ONE_TO_MANY][$modelName][$relationRef];
					if($relation->getLimit()==1) {
						if(count($relatedModels)>0) {
							$models = $relatedModels[0];
						}
						else {
							$models = NULL;
						}
					}
					else {
						$models = $relatedModels;
					}
				}
				else if($relation->getLimit()==1) {
					$models = NULL;
				}
				break;

			// M:1
			case ModelRelation::MANY_TO_ONE:
				$foreignKey = $this->getForeignKey($modelName, $relationRef);
				if(isset($this->relatedModels[ModelRelation::MANY_TO_ONE][$foreignKey])) {
					$models = $this->relatedModels[ModelRelation::MANY_TO_ONE][$foreignKey];
				}
				else {
					$models = NULL;
				}
				break;

			// M:M
			case ModelRelation::MANY_TO_MANY:
				if($relation->isRecursive()) {
					$allLinkModels = $this->getRelatedModels($relation->getLinkModel());
					foreach($allLinkModels as $ref=>$linkModels) {
						foreach($linkModels as $linkModel) {
							$rModels = $linkModel->getRelatedModels($modelName);
							unset($rModels[$relationRef]);
							foreach($rModels as $rModel) {
								if($rModel!==$this) $models[] = $rModel;
							}
						}
					}
				}
				else {
					$linkModelName = $relation->getLinkModel();
					if(isset($this->relatedModels[ModelRelation::ONE_TO_MANY][$linkModelName][$relationRef])) {
						$linkModels = $this->relatedModels[ModelRelation::ONE_TO_MANY][$linkModelName][$relationRef];
						foreach($linkModels as $linkModel) {
							$relatedModels = $linkModel->getRelatedModels($modelName, $relationRef);
							if(!is_array($relatedModels)) {
								$models[] = $relatedModels;
							}
							else {
								$models = array_merge($models, $relatedModels);
							}
						}
					}
				}
				break;

			// default
			default:
				break;
		}

		// Result
		return $models;
	}

	/*
	# @method array getRelatedModelsByCardinality( int $cardinality, [string $modelName] )
	# $cardinality	= Relationship cardinality (with $this on the LEFT of that relationship). Use ModelRelation constants for this value.
	# $modelName	= Specific target Model
	#
	# Returns all related Models with the specified relationship cardinality.
	# For 1:M relationships, you can also specify a target Model in order to return only Models of that type.
	*/
	public function getRelatedModelsByCardinality($cardinality, $modelName=NULL) {

		// Result
		if($cardinality==ModelRelation::ONE_TO_MANY && !is_null($modelName)) {
			return $this->getRelatedModels($modelName);
		}
		else {
			// Result
			return $this->relatedModels[$cardinality];
		}
	}

	/*
	# @method Model|NULL getRelatedModelByInstance( Model $model, [string $relationRef] )
	# $model		= Model instance
	# $relationRef	= Relationship reference to search on
	#
	# Returns an entry in $this->relatedModels that matches $model. This is used to basically get
	# a working reference to an entry in $this->relatedModels.
	#
	# If $relationRef is not specified, then ALL relationships that exist for the Model pair are searched.
	*/
	public function getRelatedModelByInstance($model, $relationRef=NULL) {

			// Get relation
			$relation = ModelRelation::getRelation($this->modelName, $model->modelName, $relationRef);

			// Handle multiple relationship references by calling each one individually to find $model
			if(is_array($relation)) {
				foreach($relation as $rel) {
					$rModel = $this->getRelatedModelByInstance($model, $rel->getReference());
					if(!is_null($rModel)) {
						break;
					}
				}
				reset($relation);
				return $rModel;
			}

			// Get all related Models
			$relatedModels = $this->getRelatedModels($model->modelName, $relationRef);
			if(is_null($relatedModels)) {
				return NULL;
			}
			if(!is_array($relatedModels)) {
				$relatedModels = array($relatedModels);
			}

			// Search for $model in $this->relatedModels
			// TODO: This could use a binary search algorithm as long as objects are always sorted by ID
			// TODO: This currently doesn't cater for related models being in the DB, but $model not being in the DB.
			if($model->isInDatabase()) {
				foreach($relatedModels as $rModel) {
					if($rModel->isInDatabase() && $rModel->getPrimaryKeyValue()==$model->getPrimaryKeyValue()) {
						return $rModel;
					}
				}
			}
			else {
				foreach($relatedModels as $rModel) {
					if($rModel===$model) {
						return $rModel;
					}
				}
			}

			// Result
			return NULL;
	}

	/*
	# @method void removeRelatedModel( Model $model )
	# $model	= The Model instance to be disassociated with $this Model
	#
	# Related Models are not removed from the database, only the relevant
	# foreign-keys are set to 0 (zero) and entries are remove from
	# $this->relatedModels. These changes are not committed to
	# the database until any of the Models are saved.
	#
	# TODO: What do we do with these "redundant" entries in the DB now? Leave it up to user to handle?
	# TODO: What if no related Models have been loaded into $this - they won't get removed with the code below!
	#		Maybe if $model->isInDatabase(), then just add it, then immediately remove it? Could work quite nicely?
	# TODO: If $model is NOT in the database, then for 1:M and M:1, also remove the relevant entry in $relatedModels
	#
	# UPDATE: Only use this function to remove models that you've added, but are unsaved. Calling this method and then saving WILL NOT
	# delete the removed Model. This method only removes $model from $this->relatedModels and sets any foreign-keys to -1.
	# addRelatedModel() uses this method when an association changes - so BE CAREFUL when modifying this method further!! we really need unit tests :)
	#
	# TODO: Perhaps move any removed models into a $this->removedModels[] array, so when we come to save this Model
	# we can save all the models in this->removedModels[] in order for their foreign-keys (should be -1) to be saved to the DB.
	*/
	public function removeRelatedModel($model, $relationRef=NULL, $cancelBubble=FALSE) {

		// Get relationship
		$relation = ModelRelation::getRelation($this->modelName, $model->modelName, $relationRef);

		// Handle multiple relationship references by calling each one
		// individually to find $model
		if(is_array($relation)) {
			$processed = array();
			foreach($relation as $rel) {

				// For recursive relations, manually disband both sides of the
				// relationship (parent/child)
				if($rel->isRecursive()) {

					// M:M
					// For M:M relations, we find the model that links $this and
					// $model, and disband it's relation with $this/$model
					if($rel->isManyToMany()) {
						$link = $this->getLinkingModel($model);
						if($link!==NULL) {
							$link->removeRelatedModel($this);
							$link->removeRelatedModel($model);
						}
					}

					// 1:M
					else {
						$relInv = $rel->getInverseRelation();
						$this->removeRelatedModel($model, $rel->getReference(), TRUE);
						$model->removeRelatedModel($this, $relInv->getReference(), TRUE);
						$processed[$rel->getReference()] = TRUE;
						$processed[$relInv->getReference()] = TRUE;
					}
				}

				// Disband non-recursive relationship
				else if(!isset($processed[$rel->getReference()])) {
					$this->removeRelatedModel($model, $rel->getReference());
					$processed[$rel->getReference()] = TRUE;
				}
			}
			return NULL;
		}

		// Act on relationship
		switch($relation->getCardinality()) {

			// No relationship
			case ModelRelation::NONE:
				break;

			// 1:M
			case ModelRelation::ONE_TO_MANY:

				// Remove $model's entry from $this->relatedModels
				if(isset($this->relatedModels[ModelRelation::ONE_TO_MANY][$model->modelName][$relation->getReference()])) {
					$modelPK = $model->getPrimaryKeyValue();
					foreach($this->relatedModels[ModelRelation::ONE_TO_MANY][$model->modelName][$relation->getReference()] as $k=>$rModel) {
						if($rModel===$model || ($modelPK>0 && $rModel->getPrimaryKeyValue()==$modelPK)) {

							// Unset reference in $this->relatedModels
							unset($this->relatedModels[ModelRelation::ONE_TO_MANY][$model->modelName][$relation->getReference()][$k]);

							// Remove $this from $model
							if(!$cancelBubble) {
								$model->removeRelatedModel($this, $relation->getReference());
							}

							// Exit loop
							break;
						}
					}
					reset($this->relatedModels[ModelRelation::ONE_TO_MANY][$model->modelName][$relation->getReference()]);
				}

				// Break
				break;

			// M:1
			case ModelRelation::MANY_TO_ONE:

				// Set the foreign-key on $this to -1
				// TODO: -1 is no good for string fields. Use NULL instead? Will
				// affect other areas so watch out.
				$foreignKey = $this->getForeignKey($model, $relation->getReference());
				if(!isset($this->relatedModels[ModelRelation::MANY_TO_ONE][$foreignKey]) || $this->relatedModels[ModelRelation::MANY_TO_ONE][$foreignKey]!==$model || $this->relatedModels[ModelRelation::MANY_TO_ONE][$foreignKey]->getPrimaryKeyValue()!=$model->getPrimaryKeyValue()) {
					break;
				}
				$this->{$foreignKey} = -1;

				// Remove $model's entry from $this->relatedModels
				unset($this->relatedModels[ModelRelation::MANY_TO_ONE][$foreignKey]);

				// Remove $this from $model
				if(!$cancelBubble) {
					$model->removeRelatedModel($this, $relation->getReference(), TRUE);
				}

				// Break
				break;

			// M:M
			case ModelRelation::MANY_TO_MANY:

				// Find a Model instance that links $this and $model
				$linkModel = $this->getLinkingModel($model);
				if($linkModel!==NULL) {
					$linkModel->{$linkModel->getForeignKey($this, $relation->getReference())} = -1;
					$linkModel->removeRelatedModel($this, $relation->getReference());
					$linkModel->{$linkModel->getForeignKey($model, $relation->getReference())} = -1;
					$linkModel->removeRelatedModel($model, $relation->getReference());
				}
				break;

			// Default
			default:
				break;
		}
	}

	/*
	# @method bool loadRelatedModels( [string $targetModelName, [ModelCriteria $criteria, [string $relationRef]]] )
	# $targetModelName	= Specific Model name
	# $criteria			= Apply this criteria when querying the database (USE WITH CAUTION!)
	# $relationRef		= Specific relationship for which to load Models
	#
	# Loads, from the DB, and attaches any Models related to $this Model.
	# If $targetModelName is specified then only Models of that type will be loaded.
	#
	# If $relationRef is not specified, then ALL relationships will be used when loading Models.
	#
	# NOTE: $criteria may be altered by this method, and as PHP5 passes objects
	# by reference it means it will be altered in the calling scope too.
	# Because of this, always send a "clean" criteria object to this method that
	# hasn't already been sent beforehand.
	#
	# TODO: Introduce the following syntax for enabling more efficient loading
	# of Models within nested relationships:
	#
	#	Call:
	#		$model->loadRelatedModels('Section,Category');
	#
	#	Call:
	#		$model->loadRelatedModels('Section(Page,Node)');
	#	Action:
	#		Loads all Section Models related to $model, and in turn also loads
	#		all Page and Node Models related to each Section Model (using SQL
	#		joins to make it more efficient).
	#
	#	Call:
	#		$model->loadRelatedModels('Section(Page(Statement))');
	#	Action:
	#		As above, but also loads all Statement Models related to each Page
	#		Model.
	#
	# All this happens using LEFT JOINs within a ModelCriteria instance.
	# Will need to use a DESCRIBE [table] to get all the table field names
	# (perhaps add a ModelManager::getDbColumnNames() method for this, and cache result in memory).
	# There will obviously be duplicate data in the records of this joining, so
	# need to cycle through all records and ignore duplicates. This will have to
	# happen in ModelManager::select().
	*/
	public function loadRelatedModels($targetModelName=NULL, $criteria=NULL, $relationRef=NULL) {

		// Get the relationship between $this and $targetModelName
		$relation = ModelRelation::getRelation($this->modelName, $targetModelName, $relationRef);

		if(!is_null($targetModelName)) {
			if(is_array($relation)) {
				$result = TRUE;
				foreach($relation as $rel) {
					$result = $result && $this->loadRelatedModels($targetModelName, $criteria, $rel->getReference());
				}
				return $result;
			}
			else {
				$relation = array($relation);
			}
		}

		// Load all related Models into $this
		foreach($relation as $rel) {

			// Check 'hasLoadedRelatedModels' flag first
			if(isset($this->hasLoadedRelatedModels[$rel->getTargetModel().$rel->getReference()])) {
				continue;
			}

			// Handle multiple relationship references
			if(is_array($rel)) {
				$result = TRUE;
				foreach($rel as $r) {
					$result = $result && $this->loadRelatedModels($targetModelName, $criteria, $r->getReference());
				}
				return $result;
			}

			// Retrieve Models based on their relationship cardinality
			switch($rel->getCardinality()) {
	
				// No relationship
				case ModelRelation::NONE:
					break;
	
				// 1:1
				case ModelRelation::ONE_TO_ONE:
					break;
	
				// 1:M
				case ModelRelation::ONE_TO_MANY:

					// TODO: If this is part of a recursive M:M then we need to load both the 1:M and M:1 sides of the relationship

					// Don't bother finding relations for a non-persistent Model, because it's primary key won't yet be set
					// so no foreign-keys can latch onto it in order to build relationships.
					if($this->isInDatabase()) {

						// Load Models
						$tModelName = $rel->getTargetModel();
						$tModel = Model::create($tModelName);
						//$tManager = ModelManager::get($tModelName);
						if(is_null($criteria)) {
							$C = new ModelCriteria();
							$C->addClause(ModelCriteria::EQUALS, $tModel->getForeignKey($this, $rel->getReference()), $this->getPrimaryKeyValue());
							$C->addOrder($tModel->getPrimaryKey(), 'ASC');
							$rModels = ModelManager::select($tModelName, $C);
						}
						else {
							$criteria->addClause(ModelCriteria::EQUALS, $tModel->getForeignKey($this, $rel->getReference()), $this->getPrimaryKeyValue());
							$rModels = ModelManager::select($tModelName, $criteria);
						}
						if($rel->getLimit()==1 && count($rModels)>0) {
							$this->addRelatedModel($rModels[0], $rel->getReference());
						}
						else {
							foreach($rModels as $rModel) {
								$this->addRelatedModel($rModel, $rel->getReference());
							}
						}
					}

					// If the relationship is recursive, then follow through (ie. go straight to M:1 switch case) to handle the M:1 side of the relationship
					if($rel->isRecursive()) {
						$rel = $rel->getInverseRelation();
					}
					else {
						break;
					}
	
				// M:1
				case ModelRelation::MANY_TO_ONE:

					$tModelName = $rel->getTargetModel();
					$tModel = Model::create($tModelName);
					$sForeignKey = $this->getForeignKey($tModelName, $rel->getReference());
					if(is_null($criteria)) {
						$C = new ModelCriteria();
						$C->addClause(ModelCriteria::EQUALS, $tModel->getPrimaryKey(), $this->{$sForeignKey});
						$C->addOrder($tModel->getPrimaryKey(), 'ASC');
						$rModels = ModelManager::select($tModelName, $C);
					}
					else {
						$criteria->addClause(ModelCriteria::EQUALS, $tModel->getPrimaryKey(), $this->{$sForeignKey});
						$rModels = ModelManager::select($tModelName, $criteria);
					}
					if(count($rModels)>0) {

						// If the relationship is recursive, then remember we can only
						// add recursive related Models via 1:M relationships, so we have
						// to reverse the way we add a related Model here.
						if($rel->isRecursive()) {
							$rModels[0]->addRelatedModel($this, $rel->getInverseRelation()->getReference());
						}
						else {
							$this->addRelatedModel($rModels[0], $rel->getReference());
						}
					}
					break;
	
				// M:M
				case ModelRelation::MANY_TO_MANY:

					// Get all related instances of the linking Model
					$lModelName = $rel->getLinkModel();
					$lModel = Model::create($lModelName);
					if(is_null($criteria)) {
						$C = new ModelCriteria();
						$C->addClause(ModelCriteria::EQUALS, $lModel->getForeignKey($this, $rel->getReference()), $this->getPrimaryKeyValue());
						$C->addOrder($lModel->getPrimaryKey(), 'ASC');
						$linkModels = ModelManager::select($lModelName, $C);
					}
					else {
						$criteria->addClause(ModelCriteria::EQUALS, $lModel->getForeignKey($this, $rel->getReference()), $this->getPrimaryKeyValue());
						$linkModels = ModelManager::select($lModelName, $criteria);
					}

					// Move all Models from $this->relatedModels that match entries
					// in $linkModels into $linkModels.
					foreach($linkModels as $k=>$linkModel) {
						$relatedModel = $this->getRelatedModelByInstance($linkModel);
						if(!is_null($relatedModel)) {
							$linkModels[$k] = $relatedModel;
						}
					}

					// Get all instances of target model
					// If the relationship is recursive then we need to get
					// models on the reverse side of the relationship, ie.
					// REF_PARENT/REF_CHILD.
					$ref = $rel->isRecursive() ? $rel->getInverseRelation()->getReference() : $rel->getReference();
					$tModelName = $rel->getTargetModel();
					$tModelManager = ModelManager::create($tModelName);
					$lForeignKey = $lModel->getForeignKey($tModelName, $ref);
					foreach($linkModels as $linkModel) {
						$tModel = Model::create($tModelName);
						$tModel->setPrimaryKeyValue($linkModel->{$lForeignKey});
						if($tModelManager->load($tModel)) {
							// TODO: Check that $tModel is not already loaded into $linkModel, and $linkModel is not already loaded into $this
							$linkModel->addRelatedModel($tModel, $ref);
							$this->addRelatedModel($linkModel, $rel->getReference());
						}
						unset($tModel);
					}
					break;
	
				// default
				default:
					break;
			}

			// Store flag.
			// This flag is only set if the data source has been exhausted (ie. there are no more records that can be loaded).
			// If $criteria has been defined then the source may not necessarily be exhausted, so we do not set the flag.
			if(is_null($criteria)) {
				$this->hasLoadedRelatedModels[$rel->getTargetModel().$rel->getReference()] = TRUE;
			}
		}

		// Result
		return TRUE;
	}

	/*
	# @method void clearRelatedModels( [string $targetModelName] )
	# $targetModelName	= Specific Model to clear
	#
	# Removes all instances from the relevant hashes in $this->relatedModels.
	*/
	public function clearRelatedModels($targetModelName=NULL) {

		// Clear all relations
		if(is_null($targetModelName)) {
			foreach($this->relatedModels as $k=>$v) {
				$this->relatedModels[$k] = array();
			}
			$this->hasLoadedRelatedModels = array();
			return;
		}

		// Get the relationship between $this and $targetModelName
		$relation = ModelRelation::getRelation($this->modelName, $targetModelName);

		// Retrieve Models based on their relationship cardinality
		switch($relation->getCardinality()) {

			// 1:1, 1:M
			case ModelRelation::ONE_TO_MANY:
				if(isset($this->relatedModels[ModelRelation::ONE_TO_MANY][$relation->getTargetModel()])) {
					unset($this->relatedModels[ModelRelation::ONE_TO_MANY][$relation->getTargetModel()]);
				}
				break;

			// M:1
			case ModelRelation::MANY_TO_ONE:
				$foreignKey = $this->getForeignKey($relation->getTargetModel());
				if(isset($this->relatedModels[ModelRelation::MANY_TO_ONE][$foreignKey])) {
					unset($this->relatedModels[ModelRelation::MANY_TO_ONE][$foreignKey]);
				}
				break;

			// M:M
			case ModelRelation::MANY_TO_MANY:
				// TODO
				break;

			// default
			default:
				break;
		}

		// Clear flags
		if(isset($this->hasLoadedRelatedModels[$relation->getTargetModel().$relation->getReference()])) {
			unset($this->hasLoadedRelatedModels[$relation->getTargetModel().$relation->getReference()]);
		}
	}

	/*
	# @method string getPrimaryKey()
	#
	# Returns the name of this Model's primary-key field.
	# If the primary-key is composite then this will return a comma-separated
	# string of all field names used in the key.
	*/
	public function getPrimaryKey() {

		// Result
		return $this->dbTablePrimaryKey;
	}

	/*
	# @method NULL|array getLinkingModel( Model $rModel, [string $relationRef] )
	# $rModel		= Model
	# $relationRef	= Relation reference to use when searching for the link
	#
	# Finds and returns the model(s) instance that links $this and $rModel in
	# their M:M relationship. Returns NULL if not found.
	*/
	public function getLinkingModel($rModel, $relationRef=NULL) {

		// Handle unspecified, multi-reference relations by returning the first
		// linking Model found via any of the relationships on a first-come-first
		// served basis.
		if($relationRef===NULL) {
			$relation = ModelRelation::getRelation($this->modelName, $rModel->modelName);
			if(is_array($relation)) {
				if(count($relation)==1) {
					$relation = current($relation);
				}
				else {
					foreach($relation as $rel) {
						if(($lm = $this->getLinkingModel($rModel, $rel->getReference()))!==NULL) {
							return $lm;
						}
					}
					return NULL;
				}
			}
			$relationRef = $relation->getReference();
		}

		// Find linking Model
		$relation = ModelRelation::getRelation($this->modelName, $rModel->modelName, $relationRef);
		$invRelation = $relation->getInverseRelation();
		$invRelationRef = $invRelation->getReference();

		$linkModelName = $relation->getLinkModel();
		$linkModelInst = Model::create($linkModelName);
		$tFKey = $linkModelInst->getForeignKey($this->modelName, $relationRef);
		$rFKey = $linkModelInst->getForeignKey($rModel->modelName, $invRelationRef);
		$tPKey = $this->getPrimaryKeyValue();
		$rPKey = $rModel->getPrimaryKeyValue();

		$linkModels = $this->getRelatedModels($linkModelName, $relationRef);
		foreach($linkModels as $lm) {
			if(($lm->{$tFKey}==$tPKey || $lm->getRelatedModels($this->modelName, $relationRef)===$this) &&
				($lm->{$rFKey}==$rPKey || $lm->getRelatedModels($rModel->modelName, $invRelationRef)===$rModel)) {
				return $lm;
			}
		}
		return NULL;
	}

	/*
	# @method bool hasCompositePrimaryKey()
	#
	# Returns TRUE if this Model uses a composite primary key.
	*/
	public function hasCompositePrimaryKey() {
		return $this->hasCompositePrimaryKey;
	}

	/*
	# @method string|array getPrimaryKeyValue()
	#
	# Returns the current value in this Model's primary-key field.
	# If the primary-key is composite, then this method will return an array
	# of "field-name=>field-value" pairs.
	*/
	public function getPrimaryKeyValue() {

		// Gather values of all fields involved in the primary-key
		$pk = $this->getPrimaryKey();
		if(!$this->hasCompositePrimaryKey()) {
			return $this->{$pk};
		}
		else {
			$pk = explode(",", $pk);
			$pkValue = array();
			foreach($pk as $fieldName) {
				$pkValue[$fieldName] = $this->{$fieldName};
			}
			return $pkValue;
		}
	}

	/*
	# @method string getPrimaryKeyValueToken()
	#
	# Returns a string version of the value in this Model's primary-key.
	# This is useful when you want to use the primary-key, for example, as an
	# index for an array for which you need to be sure that a string is used.
	#
	# If the primary-key is a composite then Model::getPrimaryKeyValue() will
	# return an array of values which this function will merge into a string
	# with "+" characters separating the values.
	*/
	public function getPrimaryKeyValueToken() {
		$pkValue = $this->getPrimaryKeyValue();
		return is_array($pkValue) ? implode('+', $pkValue) : $pkValue;
	}

	/*
	# @method bool setPrimaryKeyValue( mixed $value )
	# $value	= Primary key value
	#
	# Sets this Model's primary-key to $value.
	# Persistent Models cannot have their primary key changed.
	#
	# This method will be automatically executed (via __set()) if the
	# primary-key field is altered directly, eg. the following are equivalent:
	#	$this->id = 5;
	#	$this->setPrimaryKeyValue(5);
	#
	# For composite primary keys, there are two methods of setting the values
	# for each field involved in the key:
	#	$this->setPrimaryKeyValue(array(field1=>value, field2=>value));, or
	#	$this->setPrimaryKeyValue(field1, value);
	#	$this->setPrimaryKeyValue(field2, value);
	*/
	public function setPrimaryKeyValue($arg1, $arg2=NULL) {

		// Check that $this Model is not persistent.
		if($this->isInDatabase()) {
			// TODO: What about the hasChanged flag? it was set to true by __set(), but we might need it false here.
			//	Maybe move this method's code to __set and have a special case "... if($fieldName=="id") { ... } ..."
			SystemLog::add("Attempting to reset primary-key on a persistent Model", SystemLog::CORE);
			return FALSE;
		}

		// Handle standard primary key
		else if(!$this->hasCompositePrimaryKey()) {
			$this->dbData[$this->getPrimaryKey()] = $arg1;
		}

		// Handle composite primary key
		else {
			if(is_array($arg1)) {
				// TODO: Should we ensure that all array keys in $arg1 are actually primary key fields?
				foreach($arg1 as $k=>$v) {
					$this->dbData[$k] = $v;
				}
			}
			else if($arg2!==NULL) {
				$this->dbData[$arg1] = $arg2;
			}
			else {
				// TODO: THROW exception? would be better
			}
		}

		// Flag as changed
		$this->hasChanged(TRUE);
		return TRUE;
	}

	/*
	# @method string getForeignKey( string|Model $model, [string $relationRef] )
	# $model		= Name (or instance) of the Model to which the foreign-key points
	# $relationRef	= Relationship reference
	#
	# Returns the foreign-key field name used by $this Model to point to Models of type $model.
	#
	# If you don't use the recommended method of naming foreign-keys (ie. "foreign_table_id") on $this Model, then
	# you can override this method to return customized foreign-keys.
	*/
	public function getForeignKey($model, $relationRef=ModelRelation::REF_DEFAULT) {

		// TODO: Add a result cache here

		// Get Model name if passed an instance
		if($model instanceof Model) {
			$model = $model->modelName;
		}

		// Get the relationship between $this and $model
		$rel = ModelRelation::getRelation($this->modelName, $model, $relationRef);

		// Normal relation
		return $rel->isOneToMany() ? $rel->getForeignKey() : $rel->getNativeKey();
	}

	/*
	# @method string getDbConnectionName()
	#
	# Returns the name of the database connection used by this Model.
	*/
	function getDbConnectionName() {

		// Result
		return $this->dbConnectionName;
	}

	/*
	# @method string getDbTableName()
	#
	# Returns the name of the database table in which Model's of this type are stored.
	*/
	function getDbTableName() {

		// Result
		return $this->dbTableName;
	}

	/*
	# @method array getDbData()
	#
	# Returns the current contents of this Model's data fields.
	*/
	function getDbData() {

		// Result
		return $this->dbData;
	}
 
	/*
	# @method void populateFromArray( array $data )
	# $data	= Array of "fieldName=>fieldValue" pairs
	#
	# Quick method for populating this Model's fields from values in a given
	# array.
	# Note that this will in turn be calling the __set() method to store the
	# value for each field so it's not as quick as assigning values directly to
	# variables.
	*/
	function populateFromArray($data) {

		// Populate each field in the given array
		foreach($data as $k=>$v) {
			$this->{$k} = $v;
		}
	}

	/*
	# @method bool hasChanged( [bool $hasChanged] )
	# $hasChanged	= Flag state
	#
	# Returns the current value of $this->hasChanged, or sets it to the
	# specified value.
	*/
	public function hasChanged($hasChanged=NULL) {

		// Result
		return $hasChanged===NULL ? $this->hasChanged : $this->hasChanged = $hasChanged;
	}

	/*
	# @method bool isInDatabase( [bool $isInDatabase] )
	# $isInDatabase	= Flag state
	#
	# Returns the current value of $this->isInDatabase or sets it to the
	# specified value.
	*/
	public function isInDatabase($isInDatabase=NULL) {

		// Result
		return $isInDatabase===NULL ? $this->isInDatabase : $this->isInDatabase = $isInDatabase;
	}

	/*
	# @method void debug()
	#
	# Dumps some info about this Model to output.
	*/
	public function debug() {
		print "dbData:";
		print_r($this->getDbData());
		$rModels = $this->relatedModels;
		foreach($rModels as $relType=>$models) {
			foreach($models as $relRef=>$ms) {
				print "$relType:$relRef: ";
				foreach($ms as $model) {
					//print_r($model);
				}
			}
		}
	}
}
?>